iT邦幫忙

2024 iThome 鐵人賽

DAY 4
1

https://ithelp.ithome.com.tw/upload/images/20240916/20117461RFgfOjAMZw.jpg

介紹

在現代 Vue 應用中,狀態管理是一個非常重要的部分,尤其是當應用變得越來越複雜時,如何有效地管理全局狀態變得至關重要。Pinia 作為 Vue 的新一代狀態管理工具,簡潔、靈活且易於與 TypeScript 結合。在這篇文章中,我們將介紹 Pinia 的基本用法,並展示如何在 Vue 中高效地管理應用狀態。

什麼是 Pinia?

Pinia 是 Vue 3 官方推薦的狀態管理庫,它是 Vuex 的替代方案,設計更加簡單和現代化。Pinia 不僅能夠滿足大型應用的狀態管理需求,還具有與 Vue 3 的 Composition API 深度集成的特性,支持 TypeScript、插件擴展、模組化等功能。

步驟 1:安裝 Pinia

首先,我們需要在 Vue 項目中安裝 Pinia。

bun add pinia

安裝完成後,我們需要在應用中引入並初始化 Pinia。(檔案: src/main.ts)

import { createApp } from 'vue';
import App from './App.vue';
import { createPinia } from 'pinia';

const app = createApp(App);
app.use(createPinia());
app.mount('#app');

這段代碼將 Pinia 集成到 Vue 應用中,使得我們可以在全局範圍內使用 Pinia 來管理狀態。

步驟 2:創建一個 Pinia Store

Pinia 中的 store 是狀態的容器,類似於 Vuex 中的模組。我們將創建一個簡單的計數器 store 來展示 Pinia 的基本功能。這裡我們創建一個檔案 src/stores/useCounterStore.ts

備註:關於 pinia stores 的命名個人建議使用 use...Store 作為命名規則,如專案隨時間變大時,這樣比較不會和 composables 搞混,並且可以快速查找該檔案。

import { defineStore } from 'pinia';

export const useCounterStore = defineStore('counter', {
  state: () => ({
    count: 0,
  }),
  actions: {
    increment() {
      this.count++;
    },
    decrement() {
      this.count--;
    },
  },
});

在這個範例中,我們定義了一個名為 counter 的 store,裡面包含 count 狀態以及兩個修改狀態的 action:incrementdecrement

步驟 3:在 Vue 組件中使用 Pinia Store

接下來,我們將在 Vue 組件中使用剛剛創建的 Pinia store。

<template>
  <div>
    <p>當前計數:{{ counterStore.count }}</p>
    <button @click="counterStore.increment">增加</button>
    <button @click="counterStore.decrement">減少</button>
  </div>
</template>

<script lang="ts" setup>
  import { useCounterStore } from '../stores/counter';
  const counterStore = useCounterStore();
</script>

在這個範例中,我們引入了 useCounterStore,並在組件的 setup 函數中調用它。這使得我們可以在模板中直接訪問和操作 store 中的狀態和 action。

步驟 4:使用 Getters 計算衍生狀態

Pinia 也支持使用 getters 來計算衍生狀態。我們可以在 store 中定義 getter,類似於 Vue 的 computed

export const useCounterStore = defineStore('counter', {
  state: () => ({
    count: 0,
  }),
  getters: {
    doubleCount: (state) => state.count * 2,
  },
  actions: {
    increment() {
      this.count++;
    },
    decrement() {
      this.count--;
    },
  },
});

接著,我們可以在組件中使用 doubleCount 這個 getter:

<template>
  <div>
    <p>當前計數:{{ counterStore.count }}</p>
    <p>雙倍計數:{{ counterStore.doubleCount }}</p>
    <button @click="counterStore.increment">增加</button>
    <button @click="counterStore.decrement">減少</button>
  </div>
</template>

<script lang="ts" setup>
  import { useCounterStore } from '../stores/counter';
  const counterStore = useCounterStore();
</script>

這樣,我們不僅能夠顯示當前計數,還可以顯示其雙倍值,並且一旦 count 發生變化,doubleCount 也會自動更新。

步驟 5:使用 TypeScript 強化 Pinia

Pinia 與 TypeScript 完美兼容,我們可以充分利用 TypeScript 的型別推斷來增強開發體驗。在前面的範例中,Pinia 已經自動推斷了狀態和 getters 的型別,我們也可以進一步手動定義型別來提高代碼的可讀性和可維護性。

import { defineStore } from 'pinia';

interface CounterState {
  count: number;
}

export const useCounterStore = defineStore<'counter', CounterState>('counter', {
  state: (): CounterState => ({
    count: 0,
  }),
  getters: {
    doubleCount: (state) => state.count * 2,
  },
  actions: {
    increment() {
      this.count++;
    },
    decrement() {
      this.count--;
    },
  },
});

在這裡,我們定義了一個 CounterState 介面來明確描述狀態的型別。這樣做的好處是,我們的代碼變得更具可維護性,並且在開發過程中能享受 TypeScript 的型別檢查功能。

(可以整合前面的 zod infer 型別...)

步驟 6:模組化 Pinia Store

當應用變得越來越大時,我們可以將不同的狀態邏輯拆分到多個 store 中。每個 store 可以像模塊一樣進行管理,這有助於保持代碼清晰並降低維護成本。

這裡我們建立一個使用者模組 src/stores/useUserStore.ts

import { defineStore } from 'pinia';

export const useUserStore = defineStore('user', {
  state: () => ({
    name: 'John Doe',
    isLoggedIn: false,
  }),
  actions: {
    login() {
      this.isLoggedIn = true;
    },
    logout() {
      this.isLoggedIn = false;
    },
  },
});

這樣,我們可以將用戶相關的邏輯與計數器分開,並在不同的組件中使用對應的 store 來管理各自的狀態。

補充1:模組化 Pinia Store 利用 composition api

有些熟悉 pinia 的讀者,可能會認為 pinia 上述的寫法是屬於比較舊的寫法,他們已經習慣寫 composition-api 的寫法,以下我們改成 composition api 的方式
檔案位置 :src/stores/useCounterStore.ts,useUserStore 的部分 讀者也可以自行嘗試修改看看。

import { shallowRef, computed } from "vue"
import { defineStore, acceptHMRUpdate } from "pinia";

export const useCounterStore = defineStore("useCounterStore", () => {

  // state::
  const count = shallowRef<number>(0);

  // getter::
  const doubleCount = computed<number>(() => {
    return count.value * 2;
  });

  // methods::
  const increment = (): void => {
    count.value++;
  };

  const decrement = (): void => {
    count.value--;
  };

  return {
    // state::
    count,
    // getter::
    doubleCount,
    // methods::
    increment,
    decrement,
  }
})

if (import.meta.hot) {
  import.meta.hot.accept(acceptHMRUpdate(useCounterStore, import.meta.hot));
}

補充2:模組化 Pinia Store: Option store 和 setup store 的比較

關於, pinia store 有兩種寫法,這兩種有哪些優劣我們簡單分析,

傳統的 pinia 寫法(Option store):因為 state, getters, actions 分層明確,所以在維護上可以立即找到相應的位置,但在使用上相對就不會那麼彈性,且有更嚴謹的規範結構。

composition-api pinia 寫法(setup store):寫法比較彈性,但也因為非常彈性,所以開發者會比較沒有規範,對於初學者會把狀態和 method 混再一起寫,所以個人建議每一個部分分層下註解在團隊內規範會比較好。

當然兩種寫法有各自的優劣,往後的文章會講述這兩者的進階應用。

結論

Pinia 是 Vue 3 的現代化狀態管理解決方案,它具有簡單、靈活的 API,同時與 Vue 的 Composition API 和 TypeScript 無縫集成。通過本文,我們學習了如何安裝和配置 Pinia、創建和使用 store、定義 getters、使用 TypeScript 增強開發體驗,以及模組化應用中的狀態管理。

接下來的文章中,我們將進一步探討如何在更複雜的應用場景中運用 Pinia,提升狀態管理的靈活性與性能。


上一篇
Day 3: Vee-Validate 和 TypeScript 實現表單驗證的最佳實踐
下一篇
Day 5: Vue Router 與 TypeScript:型別安全的路由管理
系列文
Vue 和 TypeScript 的最佳實踐:成為前端工程師的進階利器30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

1 則留言

0
Sunny.Cat
iT邦新手 3 級 ‧ 2024-09-18 23:21:00

非常棒!原來可以這樣子寫/images/emoticon/emoticon12.gif

我要留言

立即登入留言